#include <ctype.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#define MAP_SIZE  4096UL
#define MAP_MASK (MAP_SIZE - 1)

#define FATAL do {fprintf (stderr, "Error at line %d, file %s (%d) [%s]\n", \
		__LINE__, __func__, errno, strerror(errno)); exit(1);} while (0)

#define PWM_ENABLE_REG			"0x2807e020"
#define PWM_ENABLE_VAL			"0xff"
#define GPIO_PAD_MODE_PWM0_REG	"0x32b30050"
#define GPIO_PAD_MODE_PWM0_VAL  "0x141"
#define GPIO_PAD_MODE_PWM2_REG  "0x32b30060"
#define GPIO_PAD_MODE_PWM2_VAL  "0x141"
#define PWM0_CONTROLLER         "0"
#define PWM0_CHANNEL0			"0"
#define PWM2_CONTROLLER         "2"
#define PWM2_CHANNEL0           "0"

#define WRITE_TYPE_B            "b"
#define WRITE_TYPE_H            "h"
#define WRITE_TYPE_W			"w"

#define PWM_PERIOD              "1000000"
#define PWM_DUTY_MIN            "0"
#define PWM_DUTY_MAX            PWM_PERIOD

enum PWMX {
	PWM0,
	PWM2,
	PWM0_PWM2,
	PWMMAX
};

static int pwm_config (char *attr, const char *val, int node);

//函数功能：将type指定的类型的数据写入到由addr映射的虚拟地址空间中
//type， 访问内存的类型， 半字h 字w 字节b等方式
//addr， 传入的物理地址
//data,  要写入虚拟地址空间的数据
int gpio_mode (char *addr, char *type, char *data) 
{
	int fd;
	off_t target;
	int access_type = 'w';
	void *map_base, *virt_addr;
	unsigned long writeval;

	//1 获取访问内存的物理地址 
	target = strtoul (addr, 0, 0);
	
	//2 获取数据类型
	printf ("type[0] = %s\n", type);
	access_type = tolower (type[0]);
	//3 访问内存
	if (0 > (fd = open ("/dev/mem", O_RDWR | O_SYNC)))
	{
		FATAL;
	}
	//4 将物理地址映射到内存中，映射一页物理地址到内存中（保证映射地址偏移量是4K的倍数）
	map_base = mmap (0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, target & ~MAP_MASK);
	if (map_base == (void *)-1) {
		FATAL;
	}
	//计算虚拟地址 
	virt_addr = map_base + (target & MAP_MASK); 
	writeval = strtoul (data, 0, 0);
	
	switch (access_type) {
		case 'b': 
			*((unsigned char *)virt_addr) = writeval;
			break;
		case 'h':
			*((unsigned short *)virt_addr) = writeval;
			break;
		case 'w':
			*((unsigned long *)virt_addr) = writeval;
			break;
	}
	//解除映射
	if (munmap (map_base, MAP_SIZE) == -1) FATAL;
	close (fd);
	return 0;
}

static char pwm0_path[128];
static char pwm2_path[128];

int main (int argc, char **argv) 
{
	enum PWMX pwmx = PWMMAX;
	int fd, len;

	//判断用户输入的参数是否合法
	if (argc != 2) {
		printf ("usage: ./pwm_app  <num>\n");
		printf ("num: 0, 2, 0-2\n");
		exit (-1);
	} else {
		if (strcmp (argv[1], "0") == 0) {
			pwmx = PWM0;
		} else if (strcmp (argv[1], "2") == 0) {
			pwmx = PWM2;
		} else if (strcmp (argv[1], "0-2") == 0) {
			pwmx = PWM0_PWM2;
		} else {
			printf ("usage: pwm <num>\n");
			printf ("num: 0, 2, 0-2\n");
			exit (-1);
		}
	}
	//实现gpio管脚的模式配置
	//使能pwm控制器
	gpio_mode (PWM_ENABLE_REG, (char *)WRITE_TYPE_B, PWM_ENABLE_VAL);
	//将pwm0和pwm2按照手册进行配置 
	gpio_mode (GPIO_PAD_MODE_PWM0_REG, (char *)WRITE_TYPE_H, GPIO_PAD_MODE_PWM0_VAL);
	gpio_mode (GPIO_PAD_MODE_PWM2_REG, (char *)WRITE_TYPE_H, GPIO_PAD_MODE_PWM2_VAL);

	//将pwm0/2两个节点的路径导出到pwm0/2_path 路径下
	sprintf (pwm0_path, "/sys/class/pwm/pwmchip%s/pwm%s", PWM0_CONTROLLER, PWM0_CONTROLLER);
	printf ("%s\n", pwm0_path);
	sprintf (pwm2_path, "/sys/class/pwm/pwmchip%s/pwm%s", PWM2_CONTROLLER, PWM2_CONTROLLER);
	printf ("%s\n", pwm2_path);
	//判断pwm0和pwm2设备文件有无访问权限 
	if (access(pwm0_path, F_OK)) {
		if ((fd = open ("/sys/class/pwm/pwmchip0/export", O_WRONLY)) < 0)
		{
			perror ("open error");
			exit (-1);
		}
		len = strlen (PWM0_CHANNEL0);
		if (len != write (fd, PWM0_CHANNEL0, len)) {
			perror ("write error");
			close (fd);
			exit (-1);
		}
		close (fd);
	}

	if (access(pwm2_path, F_OK)) {
		if ((fd = open ("/sys/class/pwm/pwmchip2/export", O_WRONLY)) < 0)
		{
			perror ("open error");
			exit (-1);
		}
		len = strlen (PWM2_CHANNEL0);
		if (len != write (fd, PWM2_CHANNEL0, len)) {
			perror ("write error");
			close (fd);
			exit (-1);
		}
		close (fd);
	}
	
	//如果说节点可以正常导出, 接下来就可以调节占空比了
	//配置pwm0的持续周期1s
	if (pwm_config ("period", PWM_PERIOD, 0) < 0) {
		fprintf (stderr, "config pwm0 period attribute failed!");
		exit (-1);
	}
	
	//初始化占空比为0
	if (pwm_config ("duty_cycle", PWM_DUTY_MIN, 0) < 0) {
		fprintf (stderr, "config pwm0 duty attribute failed!");
		exit (-1);
	}

	//使能pwm0
	if (pwm_config ("enable", "1", 0) < 0) {
		fprintf (stderr, "config pwm0 enable failed!");
		exit (-1);
	}
	//配置pwm2的持续周期1s
	if (pwm_config ("period", PWM_PERIOD, 1) < 0) {
		fprintf (stderr, "config pwm2 period attribute failed!");
		exit (-1);
	}
	
	//初始化占空比为0
	if (pwm_config ("duty_cycle", PWM_DUTY_MIN, 1) < 0) {
		fprintf (stderr, "config pwm2 duty attribute failed!");
		exit (-1);
	}

	//使能pwm2
	if (pwm_config ("enable", "1", 1) < 0) {
		fprintf (stderr, "config pwm2 enable failed!");
		exit (-1);
	}

	char increase = 100; 
	int step; 
	unsigned int duty1 = atoi (PWM_DUTY_MIN) + 500;
	unsigned int duty2 = atoi (PWM_DUTY_MAX) - 500;
	unsigned int duty = duty1;
	char dutys[128];

	step = (duty2 - duty1) / 100;
	printf ("duty1 = %d, duty2 = %d, step = %d\n", duty1, duty2, step);

	while (1) {
		if (increase) {
			duty += step;
		} else {
			duty -= step;
		}
		
		memset (dutys, 0, sizeof (dutys) / sizeof (dutys[0]));
		sprintf (dutys, "%d", duty);

		//根据用户的输入, 决定使用那一路pwm
		//如果使用pwm0这一路, 就直接配置pwm2为最小的占空比
		//反之亦然
		if (pwmx == PWM0) {
			if (pwm_config ("duty_cycle", PWM_DUTY_MIN, 1) < 0)
			{
				fprintf (stderr, "config pwm duty error!\n");
				exit (-1);
			}
			if (pwm_config ("duty_cycle", dutys, 0) < 0) {
				fprintf (stderr, "config pwm duty error!\n");
				exit (-1);
			}
		} else if (pwmx == PWM2) {
			if (pwm_config ("duty_cycle", PWM_DUTY_MIN, 0) < 0) {
				fprintf (stderr, "config pwm duty error!\n");
				exit (-1);
			}
			if (pwm_config ("duty_cycle", dutys, 1) < 0) {
				fprintf (stderr, "config pwm duty error!\n");
				exit (-1);
			}
		} else if (pwmx == PWM0_PWM2) {
			if (pwm_config ("duty_cycle", dutys, 0) < 0) {
				fprintf (stderr, "config pwm duty error!\n");
				exit (-1);
			}
			if (pwm_config ("duty_cycle", dutys, 1) < 0) {
				fprintf (stderr, "config pwm duty error!\n");
				exit (-1);
			}
		}
		
		if (duty >= duty2) {
			increase = 0;
		}
		if (duty <= duty1) {
			increase = 1;
		}
		usleep (20000); // 20ms
	}
}

static int pwm_config (char *attr, const char *val, int node) 
{
	char file_path[256];
	int fd, len;

	if (node == 0) {
		if ((strlen (pwm0_path) + strlen (attr)) > (sizeof (file_path) / sizeof (char)))  {
			fprintf (stderr, "filepath too long!\n");
			return -1;
		}
		//file_path存储的是pwm设备节点文件的属性文件的路径
		sprintf (file_path, "%s/%s", pwm0_path, attr);
		printf ("%s\n", file_path);
	}
	else if (node == 1) {
		if ((strlen (pwm2_path) + strlen (attr)) > (sizeof (file_path) / sizeof (char))) {
			fprintf (stderr, "filepath too long!\n");
			return -1;
		}
		sprintf (file_path, "%s/%s", pwm2_path, attr);
		printf ("%s\n", file_path);
	}
	if ((fd = open (file_path, O_WRONLY)) < 0) {
		perror ("open error");
		close (fd);
		return -1;
	}
	len = strlen (val);
	printf ("len = %2d val = %10s\n", len, val);
	
	if (write (fd, val, len) != len){
		perror ("write error");
		close (fd);
		return -1;
	}
	close (fd);
	return 0;
}

